package modelo;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Observable;
import java.util.Vector;
import modelo.estados.Estado;
import modelo.exception.CrearProyectoException;
import modelo.exception.ExistingUserException;
import modelo.exception.MemberNotFoundException;
import modelo.exception.MissingDataException;
import modelo.exception.NoPuedeIniciarException;
import modelo.exception.SameStateException;
import modelo.exception.StatusChangeException;
import modelo.exception.TaskNotExistException;
import modelo.tareas.Tarea;
import modelo.tareas.TareaCompuesta;
import modelo.tareas.TareaSimple;
import modelo.util.MyDate;
import modelo.util.Rol;

/** 
 * @author Natalia Stefania
 */
public class Proyecto extends Observable{
	
	// Variables  *********************************************************** //
	
	private String nombre; 
	private List <Tarea> tareas; 
	private List <Usuario> miembros;
	private List <Usuario> miembrosHistoricos;  
	private List <Asignacion> asignaciones; 

	//Constructor *********************************************************** //
	// deberia validar que no se creen dos proyectos con el mismo id
	public Proyecto(String id) throws CrearProyectoException {
		
		this.nombre = id;
		if(id.contentEquals("")){
			throw new CrearProyectoException("El proyecto debe tener un nombre");
		}
		this.tareas = new ArrayList<Tarea>();
		this.miembros = new ArrayList<Usuario>();
		this.miembrosHistoricos= new ArrayList<Usuario>(); 
		this.asignaciones= new ArrayList<Asignacion>(); 
		
	}
	
	public Proyecto(String id, List<Tarea> tareas, List<Usuario> miembros, List <Usuario> miembrosHistoricos, List<Asignacion> asignaciones) {
		this.nombre = id;
		this.tareas = tareas;
		this.miembros = miembros;
		this.miembrosHistoricos= miembrosHistoricos; 
		this.asignaciones= asignaciones; 
	}
	
	//Metodos     *********************************************************** //
	
	/*
	 * Crea una tarea para cada constructor. Las tareas pueden ser simples y compuestas.
	 */
	public void crearTareaSimple (String name, int importancia, String finalizacionEstimada, int estimacion) throws MissingDataException{
		if(! name.equals("") && !(importancia==0) && ! finalizacionEstimada.equals ("") && !(estimacion==0) ){
			Tarea tarea = new TareaSimple (name, importancia, finalizacionEstimada, estimacion); 
			this.tareas.add(tarea); 
		}
		else {
			throw new MissingDataException("Falta completar datos");
		}
		onChange();
	}
	
	public void crearTareaSimple (String name, int importancia, String finalizacionEstimada, int estimacion, Tarea predecesor) throws MissingDataException{
		if(! name.equals("") && !(importancia==0) && ! finalizacionEstimada.equals ("") && !(estimacion==0)){
			Tarea tarea = new TareaSimple (name, importancia, finalizacionEstimada, estimacion, predecesor); 
			this.tareas.add(tarea); 
		}
		else {
			throw new MissingDataException("Falta completar datos");
		}
		onChange();
	}
	
	public void crearTareaCompuesta (String name, int importancia, String finalizacionEstimada, int estimacion, List<Tarea> tareas) throws MissingDataException{
		if(! name.equals("") && !(importancia==0) && ! finalizacionEstimada.equals ("") && !(estimacion==0)){
			Tarea tarea= new TareaCompuesta (name, importancia, finalizacionEstimada, estimacion, tareas); 
			this.tareas.add(tarea); 
		}
		else {
			throw new MissingDataException("Falta completar datos");
		}
		onChange();
	}
	
	public void crearTareaCompuesta (String name, int importancia, String finalizacionEstimada, int estimacion, Tarea predecesor, List<Tarea> tareas) throws MissingDataException{
		if(! name.equals("") && !(importancia==0) && ! finalizacionEstimada.equals ("") && !(estimacion==0)){
			Tarea tarea= new TareaCompuesta (name, importancia, finalizacionEstimada, estimacion, predecesor, tareas); 
			this.tareas.add(tarea);
		}
		else {
			throw new MissingDataException("Falta completar datos");
		}
		onChange();
	}
	
	/**
	 * @throws ExistingUserException
	 * @
	 * Si el usuario, el cual quiero agregar, no se encuentra en la lista de miembros del proyecto, lo agrego; 
	 * en caso contrario, lanzo una excepcion manejando esa posiblidad
	 */
	public void agregarMiembro (Usuario user) throws ExistingUserException { 
		if (! this.miembros.contains(user)){
			this.miembros.add(user);
		}
		else{
			throw new ExistingUserException("El miembro ya pertenece a la lista de usuarios del proyecto");
		}
	}
	
	/**
	 * @throws MemberNotFoundException
	 * @
	 * Si el miembro es parte del proyecto lo elimina; en caso contrario, manejara ese error.
	 */
	public void eliminarMiembro (Usuario user) throws MemberNotFoundException{
		if (this.miembros.contains(user)){
			this.miembros.remove(user);
			this.miembrosHistoricos.add(user); 
		}
		else {
			throw new MemberNotFoundException ("El usuario no existe dentro del proyecto"); 
		}
	}
	
	/**
	 * @throws MemberNotFoundException
	 * @
	 * Si la lista de miembros se encuentra el usuario pedido, a ese le agrego un rol. Si ese usuario tiene 
	 * un rol lo va a reemplazar por el dado como parametro. Si no se encuentra, lanza excepcion. 
	 */
	public void asignarRol (Usuario miembro, Rol rol) throws MemberNotFoundException{ 
		if(this.miembros.contains(miembro)){
			miembro.addRolActual(this, rol); 	
		}
		else{
			throw (new MemberNotFoundException("El usuario no existe dentro del proyecto"));
		}
	}
	
	/**
	 * Si la lista de tareas del proyecto contiene a una tarea determinada, le va a cambiar el nombre a la misma.
	 * En caso que no la encuentre, va a lanzar una excepcion. 
	 */
	public void modificarNombreTarea (Tarea t, String name){
		for (Tarea tarea: this.getTareas()){
			if (tarea.equals(t)){
				tarea.setNombre(name); 
			}
		}
	}
	
	/**
	 * Si la lista de tareas del proyecto contiene a una tarea determinada, le va a cambiar la importancia de dicha tarea.
	 * En caso que no la encuentre, va a lanzar una excepcion. 
	 */
	public void modificarImportanciaTarea (Tarea t, int importance) {
		for (Tarea tarea: this.getTareas()){
			if (tarea.equals(t)){
				tarea.setImportancia(importance); 
			}
		}
		this.onChange();
	}
	
	/**
	 * Si la lista de tareas del proyecto contiene a una tarea determinada, le va a cambiar la estimacion de la misma.
	 * En caso que no la encuentre, va a lanzar una excepcion. 
	 */
	public void modificarEstimacionTarea (Tarea t, int estimacion){
		for (Tarea tarea: this.getTareas()){
			if (tarea.equals(t)){
				tarea.setEstimacion(estimacion); 
			}
		}
	}
	
	/**
	 * Si en la lista de tareas que contiene el proyecto, se encuentra una determinada tarea, le va a cambiar
	 * el dia de la finalizacion que se estima que termine la tarea el usuario. 
	 */
	public void modificarFinalizacionEstimadaTarea (Tarea tarea, String finEst){
		for (Tarea tarea1: this.getTareas()){
			if (tarea1.equals(tarea)){
				tarea1.setFinalizacionEstimada(new MyDate(finEst).getFechaYHora()); 
			}
		}
	}
	
	/**
	 * @param tarea
	 * @
	 * recorre la coleccion de tareas, cuando encuentra la tarea pasada como parametro la remueve
	 */
	public void eliminarTarea (Tarea tarea) {
		// Use un for, en vez de un for each. Ya que este ultimo, tenia problemas al hacer correr
		// exactamente el test de este mismo metodo. Este problema (segun Alexis) es que al correr 
		// el test, estaba tomando un caso que no deberia tomar. 
		for (int i = 0; i < this.getTareas().size(); i++) {
			if (this.tareas.get(i).equals(tarea)){
				this.tareas.remove(this.tareas.get(i)); 
			}
		}
	}
	
	public void onChange (){
		this.setChanged();
		this.notifyObservers(null);
	}
	
	/**
	 * @throws TaskNotExistException 
	 * @throws StatusChangeException 
	 * @throws SameStateException 
	 * @throws NoPuedeIniciarException 
	 * @
	 * recorre la coleccion de tareas, cuando encuentra la tarea pasada como parametro devuelve el estado de dicha tarea.
	 */
	public void reabrirTarea (Tarea tarea, String nota) throws TaskNotExistException, SameStateException, StatusChangeException, NoPuedeIniciarException{
		if(this.getTareas().contains(tarea)){
			tarea.reabrirTarea(nota);
		}
		else {
			throw (new TaskNotExistException("No existe esta tarea dentro del proyecto"));
		}
	}
	
	/**
	 * @return Estado
	 * @
	 * Indica que en estado se encuentra una tarea determinada. 
	 */
	public Estado estadoTarea (Tarea tarea){
		return tarea.getEstado(); 
	}

	/**
	 *  @param tarea 
	 *  @
	 *  Se le asigna a un usuario del proyecto, una tarea del mismo. Al asignar la tarea, la fecha inicial va a ser la fecha actual.
	 */
	public void asignarTarea (Usuario miembro, Tarea tarea, Date fechaFinal) {
		if (this.miembros.contains(miembro) && this.tareas.contains(tarea)){
				Asignacion asignacionMiembro = new Asignacion (new Date (), fechaFinal, miembro, tarea);
				this.asignaciones.add(asignacionMiembro); 
			}
	}
	
	/**
	 * @return List<Usuario>
	 * @
	 * Aquellos usuarios que tienen asignado un rol determinado
	 */
	public List<Usuario> miembrosDelRol (Rol rol){
		List <Usuario> miembrosDeUnRol = new LinkedList <Usuario>(); 
		for (Usuario user: this.miembros){
			if (user.getMimap().containsValue(rol)){
				miembrosDeUnRol.add(user);
			}
		}
		return miembrosDeUnRol; 
	}

	/**
	 * @return Vector<Rol>
	 * @
	 * Aquellos roles que tiene asignado un miembro del proyecto.
	 */
	public Vector <Rol> rolesDeMiembro (String nombreUsuario){ 
		Vector <Rol> v= new Vector <Rol>();
		
		for (Usuario user: this.getMiembros()){
			if (user.getNombrePersona().equals(nombreUsuario)){	
				v.addAll(user.getMimap().values()); 
			}
		}
		return v;
	}
	
	/**
	 * @return List<Tarea>
	 * @
	 * Indica que tareas tienen asignadas un determinado miembro.
	 */
	public List <Tarea> tareasDeMiembro (String nombreUsuario){
		List <Tarea> misTareas= new LinkedList <Tarea> (); 
		for (Usuario user: this.getMiembros()){
			if (user.getNombrePersona()== nombreUsuario){
				misTareas= user.getTareasALasQuePertenezco(); 
			}
		}
		return misTareas;
	}
	
	/**
	 * @return List<Tarea>
	 * @
	 * De las tareas que contiene un proyecto, sabremos las asignaciones que son anteriores a la fecha actual.
	 */
	public List <Tarea> asignacionesAnteriores (){
		List <Tarea> asigAnteriores= new LinkedList <Tarea>();
		for (Tarea t: this.tareas){
			if (t.getFechaFinalizacion().before(new Date())){
				asigAnteriores.add(t);
			}
		}
		return asigAnteriores;
	}
	
	/**
	 * @return List<Tarea>
	 * @
	 * De las tareas que contiene un proyecto, sabremos las asignaciones que son posteriores o iguales a la fecha actual.
	 */
	public List <Tarea> asignacionesActuales (){
		List <Tarea> asigActuales= new LinkedList <Tarea>();
		for (Tarea t: this.tareas){
			if (t.getFechaFinalizacion().compareTo(new Date())>=0){
				asigActuales.add(t);
			}
		}
		return asigActuales;
	}
	
	public List<Asignacion> asignacionesDeUnaTarea (Tarea t){
		List <Asignacion> asigDeUnaTarea= new LinkedList <Asignacion>();
		for (Asignacion asig: this.getAsignaciones() ){
			if (asig.getTareaAsignada()== t){
				asigDeUnaTarea.add(asig);
			}
		}
		return asigDeUnaTarea; 
	}
	
	// INFORMES ESTADISTICOS
	
	// AUXILIAR
	public Integer horasTrabajadasDeUnaPersona (String user){
		Integer contadorDeHs =0; 
		for (Tarea t: this.tareasDeMiembro(user)){
			contadorDeHs += t.getDuracion(); 
		}
		return contadorDeHs;
	}
	
	/**
	 * @return HashMap 
	 * @
	 * Devuelve cuanto tiempo trabajado por una persona en ese proyecto.
	 */
	public HashMap <Usuario, Integer> tiempoPorPersona(){
		Map <Usuario, Integer> mapaDetallado = new HashMap <Usuario, Integer>(); 
		for (Usuario user: this.miembros){
			mapaDetallado.put(user, this.horasTrabajadasDeUnaPersona(user.getNombrePersona())); 
		}
		return (HashMap<Usuario, Integer>) mapaDetallado; 
	}
	
	
	/**
	 * @return int
	 * @
	 * Devuelve cuanto tiempo se tardo en terminar todas las tarea del proyecto. Es decir, las horas totales trabajadas en el mismo.  
	 */
	public int horasTotalesTrabajadas (){
		int contadorDeHs=0; 
		for (Tarea t: this.tareas){
			contadorDeHs= t.getDuracion()+ contadorDeHs; 
		}
		return contadorDeHs; 
	}
	
	// AUXILIAR
	public int tareasFinalizadas (){
		int tareasFinalizadas= 0; 
		for (Tarea t: this.tareas){
			if (t.estaFinalizado()){
				tareasFinalizadas ++; 
			}
		}
		return tareasFinalizadas;
	}
	
	/**
	 * @return float
	 * @
	 * Devuelve el porcentaje de las tareas realizadas por el proyecto. Este es el informe de carga de trabajo. 
	 */
	public float porcentajeRealizado (){
		int cantFinish = this.tareasFinalizadas();
		int cantTotal = this.tareas.size(); 
		return (cantFinish*100)/cantTotal; 
	}
	
	//GET&SET     *********************************************************** //
	
	public String getNombre() {
		return nombre;
	}
	
	public void setNombre(String nombre) {
		this.nombre = nombre;
	}
	
	public List<Tarea> getTareas() {
		return tareas;
	}
	
	public void setTareas(List<Tarea> tareas) {
		this.tareas = tareas;
	}
	
	public List<Usuario> getMiembros() {
		return miembros;
	}
	
	public void setMiembros(List<Usuario> miembros) {
		this.miembros = miembros;
	}
	
	public List<Asignacion> getAsignaciones() {
		return asignaciones;
	}
	
	public void setAsignaciones(List<Asignacion> asignaciones) {
		this.asignaciones = asignaciones;
	}

	public void setMiembrosHistoricos(List <Usuario> miembrosHistoricos) {
		this.miembrosHistoricos = miembrosHistoricos;
	}

	public List <Usuario> getMiembrosHistoricos() {
		return miembrosHistoricos;
	} 
}